index.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. <template>
  2. <div
  3. ref="page"
  4. class="flex flex-col gap-2 p-2 overflow-y-auto bg-background-200"
  5. >
  6. <div
  7. ref="header"
  8. class="right-0 left-0 top-0 fixed"
  9. >
  10. <img
  11. v-if="news"
  12. :src="news.preview.length > 0 ? news.preview : './notfound.png'"
  13. class="w-full object-cover object-center absolute -z-10"
  14. :style="`height: ${imageHeight}px`"
  15. >
  16. <div
  17. v-else
  18. class="w-full object-cover object-center absolute -z-10 loading"
  19. :style="`height: ${imageHeight}px`"
  20. />
  21. <div
  22. class="flex w-full flex-col"
  23. :style="`
  24. height: ${imageHeight}px;
  25. background: linear-gradient(to bottom, #212121, rgba(33,33,33,${blur/10.5}), #212121)
  26. `"
  27. >
  28. <div
  29. ref="blurry"
  30. :class="`w-full h-full transition-all`"
  31. :style="`backdrop-filter: blur(${blur}px); background-color: rgba(33,33,33,0);`"
  32. >
  33. <IArrowLeftAlt
  34. class="absolute top-0 left-2 w-12 h-12 hover:opacity-50 duration-150 z-20 animate__animated animate__fadeInLeft animate__duration"
  35. :style="direction === 'right' && lengthX < -100 ? `left: 20px;` : ''"
  36. @click="$router.back()"
  37. />
  38. <div
  39. v-if="news"
  40. class="font-semibold absolute bottom-2.5 truncate w-full pr-4 show"
  41. :style="`padding-left: ${padLeft}px; font-size: ${textSize}px; line-height: ${lineHeight}px; ${titleStyles};`"
  42. v-text="news.title"
  43. />
  44. </div>
  45. </div>
  46. </div>
  47. <div
  48. v-if="news"
  49. class="flex flex-col min-h-screen h-[2000px] news-content"
  50. :style="`padding-top: ${headerHeightMax}px`"
  51. v-html="news.content"
  52. />
  53. <div
  54. v-else
  55. class="flex flex-col min-h-screen items-center justify-center news-content"
  56. :style="`padding-top: ${headerHeightMax}px`"
  57. >
  58. <ILoader class="w-10 h-10" />
  59. </div>
  60. </div>
  61. </template>
  62. <script setup lang="ts">
  63. import { useWindowScroll, useSwipe } from '@vueuse/core'
  64. interface News {
  65. content: string
  66. date: string
  67. id: number
  68. title: string
  69. }
  70. definePageMeta({
  71. middleware: ['user-only'],
  72. layout: 'news',
  73. })
  74. const headerHeightMax = 275
  75. const headerHeightMin = 50
  76. const backButtonHeight = 48
  77. const titleLeftPaddingMin = 10
  78. const imageHeight = ref(headerHeightMax)
  79. const blur = ref(0)
  80. const page = ref(null)
  81. const padLeft = ref(titleLeftPaddingMin)
  82. const textSize = ref(24)
  83. const lineHeight = ref(32)
  84. const titleStyles = ref('')
  85. const route = useRoute()
  86. const api = useApi()
  87. const news = ref<null | News>()
  88. const { y } = useWindowScroll({ behavior: 'smooth' })
  89. const { direction, lengthX } = useSwipe(page, {
  90. onSwipeEnd() {
  91. if (direction.value === 'right' && lengthX.value < -100) useRouter().back()
  92. },
  93. })
  94. watch(y, (val) => {
  95. imageHeight.value = Math.max(headerHeightMin, headerHeightMax - val)
  96. blur.value = Math.ceil((headerHeightMax - imageHeight.value) / 25)
  97. if (imageHeight.value > backButtonHeight * 2) {
  98. padLeft.value = titleLeftPaddingMin
  99. textSize.value = 24
  100. lineHeight.value = 32
  101. }
  102. else {
  103. padLeft.value = titleLeftPaddingMin + (-imageHeight.value + (backButtonHeight * 2)) * 1.5
  104. textSize.value = Math.min(24, imageHeight.value - backButtonHeight + 16)
  105. lineHeight.value = 32
  106. }
  107. })
  108. onMounted(async () => {
  109. news.value = await api.get<News>(`/news/id${route.params.id}?md=false`)
  110. })
  111. </script>